﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Bling.DSL;
using Bling.Properties;
using Bling.Util;
using Bling.Physics;
using Bling.WPF;
using Bling.Animation;

namespace Bling.Physics {
  public static class ParticleSystem {
    public static Impl<BLOCK> ParticleSystemE<BLOCK>(this PhysicsBlock<BLOCK> block)
      where BLOCK : PhysicsBlock<BLOCK>, Is<BLOCK> {
      return block.Extension<Impl<BLOCK>>();
    }
    public static DoubleBl Age<BLOCK>(this PhysicsBlock<BLOCK>.Body body)
      where BLOCK : PhysicsBlock<BLOCK>, Is<BLOCK> {
      return body.GetProperty(body.Block.ParticleSystemE().AgeProperty);
    }
    public static DoubleBl TotalAge<BLOCK>(this PhysicsBlock<BLOCK>.Body body)
      where BLOCK : PhysicsBlock<BLOCK>, Is<BLOCK> {
      return body.GetProperty(body.Block.ParticleSystemE().TotalAgeProperty);
    }
    public static DoubleBl AgeRatio<BLOCK>(this PhysicsBlock<BLOCK>.Body body)
      where BLOCK : PhysicsBlock<BLOCK>, Is<BLOCK> {
      return body.Age() / body.TotalAge();
    }
    public static Action<int, double> SetAge<BLOCK>(this PhysicsBlock<BLOCK> block)
      where BLOCK : PhysicsBlock<BLOCK>, Is<BLOCK> {
      var a0 = DoubleBl.Assign<int, double>((idx, unused) => block[idx].Age(), (unused, value) => value);
      var a1 = DoubleBl.Assign<int, double>((idx, unused) => block[idx].TotalAge(), (unused, value) => value);
      return (idx, value) => {
        a0(idx, value);
        a1(idx, value);
      };
    }
    public interface Is<BLOCK> : HasBlockExtension<BLOCK, Impl<BLOCK>>
      where BLOCK : PhysicsBlock<BLOCK>, Is<BLOCK> { }

    public class Impl<BLOCK> : BlockExtension<Impl<BLOCK>, BLOCK>
      where BLOCK : PhysicsBlock<BLOCK>, Is<BLOCK> {
      public Func<BLOCK, Action<int>> InitEmit;
      public Func<BLOCK, double> EmitNew;
      public Func<PhysicsBlock<BLOCK>.Body, DoubleBl> Decay = (body) => 10d;
      internal readonly PhysicsProperty<double> AgeProperty;
      internal readonly PhysicsProperty<double> TotalAgeProperty;
      public Impl(BLOCK block)
        : base(block) {
        AgeProperty = block.NewBodyProperty<double>("Age");
        TotalAgeProperty = block.NewBodyProperty<double>("TotalAge");
      }
      protected internal override void DoInit(BLOCK block) {
        block.ForAll(body => {
          body.TotalAge().Init = 1d;
          body.Age().Init = 0d;
          //(body.AgeRatio() > 0d).Link.To = body.At.Bl0().Visibility;
          // age decay.
          body.Age().Bind = (body.Age() - Decay(body)).Max(0);
        });
      }
      private double leftOver = 0d;
      protected internal override void DoActivate(BLOCK block) {
        base.DoActivate(block);
        Func<bool>[] Free = new Func<bool>[block.Count];
        for (int i = 0; i < Free.Length; i++)
          Free[i] = (block[i].AgeRatio() <= 0d).AsFunc;
        Action<int> Init = InitEmit(block);
        block.World.UpdateInUI.Add(() => {
          double add = EmitNew(block) + leftOver;
          for (int i = 0; i < block.Count && add >= 1d; i++) {
            if (Free[i]()) {
              add--;
              Init(i);
            }
          }
          leftOver = Math.Min(add, 1d);
        });
      }
    }
  }


  public static class ParticleSystemUI {
    public static Impl<T,BLOCK> ParticleSystemUIE<T, BLOCK>(this PhysicsBlock<T, BLOCK> block)
      where BLOCK : PhysicsBlock<T, BLOCK>, Is<T, BLOCK>, ParticleSystem.Is<BLOCK>, RigidBody.Is<BLOCK>
      where T : FrameworkElement {
      return block.Extension<Impl<T, BLOCK>>();
    }
    public interface Is<T, BLOCK> : HasBlockExtension<BLOCK, Impl<T, BLOCK>>
      where BLOCK : PhysicsBlock<T, BLOCK>, Is<T, BLOCK>, ParticleSystem.Is<BLOCK>, RigidBody.Is<BLOCK>
      where T : FrameworkElement {
    }
    public class Impl<T, BLOCK> : BlockExtension<Impl<T, BLOCK>, BLOCK>
      where BLOCK : PhysicsBlock<T, BLOCK>, Is<T, BLOCK>, ParticleSystem.Is<BLOCK>, RigidBody.Is<BLOCK>
      where T : FrameworkElement {

      /// <summary>
      /// Used to add variation to the particle system.  By default, a random number with a time-dependent seed. 
      /// </summary>
      public Random Random = new Random();
      public Action<Random, BLOCK, int> ParticleInit = (r, b, idx) => { };
      public Impl(BLOCK block) : base(block) { }
      protected internal override void DoInit(BLOCK block) {
        ForceInit<ParticleSystem.Impl<BLOCK>>(block);
        ForceInit<RigidBody.Impl<BLOCK>>(block);
        for (int i = 0; i < block.Count; i++) {
          // enable scale and rotation.
          block.Get(i).Bl().EnableAtRotation = block.Get(i).Bl().CenterSize;
          block.Get(i).Bl().EnableAtScale = block.Get(i).Bl().CenterSize;
        }
        block.ForAll((PhysicsBlock<T, BLOCK>.Body body) => {
          body.Rotation().Init = 0.ToPI();
          body.Scale().Init = 1d;

          (body.Rotation().Inverse - .5.ToPI()).Link.To = body.At.Bl0().AtRotation.ToRadians();

          body.Center().Link.To = body.At.Bl0().CenterPosition;
          (body.AgeRatio() > 0d).Link.To = body.At.Bl0().Visibility;

          (new PointBl(body.Scale(), body.Scale())).Link.To = body.At.Bl0().AtScale;

        });
      }
    }
  }
}